/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.io;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * ResolverUtil用于获取指定包下所有满足一定条件的类集合。
 * <p>
 * 在其内部定义了一个Test接口，用于判断类是否符合条件。
 * <p>
 * 属性{@link #classloader}用于完成类的扫描加载工作，默认实现是{@link Thread#currentThread()#getContextClassLoader()}。
 * <p>
 * 两个默认的{@link Test}实现：
 * {@link IsA}用于判断某个类是否是指定类型的子类或者实现类。
 * {@link AnnotatedWith}用于判断某个类上是否标注了指定的注解。
 *
 * @author Tim Fennell
 */
public class ResolverUtil<T> {
    /*
     * Log实现
     */
    private static final Log log = LogFactory.getLog(ResolverUtil.class);

    /**
     * 用于筛选类的条件测试接口定义
     */
    public interface Test {
        /**
         * 判断传入的类是否满足必要的条件
         */
        boolean matches(Class<?> type);
    }

    /**
     * 校验某个类是否是指定类的子类或者实现类
     */
    public static class IsA implements Test {
        /**
         * 父类或者接口
         */
        private Class<?> parent;

        /**
         * 构造
         */
        public IsA(Class<?> parentType) {
            this.parent = parentType;
        }

        /**
         * 判断某个类是否指定类的子类或者实现类
         */
        @Override
        public boolean matches(Class<?> type) {
            return type != null && parent.isAssignableFrom(type);
        }

        @Override
        public String toString() {
            return "is assignable to " + parent.getSimpleName();
        }
    }

    /**
     * 用于检查指定的类上是否标注了指定注解的测试类
     */
    public static class AnnotatedWith implements Test {
        /**
         * 用于校验的注解类
         */
        private Class<? extends Annotation> annotation;

        /**
         * 构造
         */
        public AnnotatedWith(Class<? extends Annotation> annotation) {
            this.annotation = annotation;
        }

        /**
         * 判断指定类上是否标注了指定的注解
         */
        @Override
        public boolean matches(Class<?> type) {
            return type != null && type.isAnnotationPresent(annotation);
        }

        @Override
        public String toString() {
            return "annotated with @" + annotation.getSimpleName();
        }
    }

    /**
     * 满足条件的类型集合
     */
    private Set<Class<? extends T>> matches = new HashSet<>();

    /**
     * 用于扫描类的类加载器
     */
    private ClassLoader classloader;

    /**
     * 获取所有匹配条件的类型集合
     *
     * @return 所有匹配条件的类型集合
     */
    public Set<Class<? extends T>> getClasses() {
        return matches;
    }

    /**
     * 获取用于扫描类的类加载器，默认使用{@link Thread#currentThread()#getContextClassLoader()}
     *
     * @return 用于扫描类的类加载器
     */
    public ClassLoader getClassLoader() {
        return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
    }

    /**
     * 配置用于扫描类的类加载器
     *
     * @param classloader 用于扫描类的类加载器
     */
    public void setClassLoader(ClassLoader classloader) {
        this.classloader = classloader;
    }

    /**
     * 获取指定包集合下，所有指定类/接口的子类/实现类。
     *
     * @param parent       用于查找子类或者实现类的类定义/接口定义
     * @param packageNames 用于查找类的一个或多个包名
     */
    public ResolverUtil<T> findImplementations(Class<?> parent, String... packageNames) {
        if (packageNames == null) {
            return this;
        }
        // 判断是否是指定类型的子类或者实现类
        Test test = new IsA(parent);
        for (String pkg : packageNames) {
            // 挨个处理包
            find(test, pkg);
        }
        return this;
    }

    /**
     * 获取指定包集合下所有标注了指定注解的类集合
     *
     * @param annotation   应被标注的注解
     * @param packageNames 一个或多个包名
     */
    public ResolverUtil<T> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
        if (packageNames == null) {
            return this;
        }
        // 判断是否有指定注解
        Test test = new AnnotatedWith(annotation);
        for (String pkg : packageNames) {
            find(test, pkg);
        }

        return this;
    }

    /**
     * 将包名转换为文件路径
     *
     * @param packageName 包名
     */
    protected String getPackagePath(String packageName) {
        return packageName == null ? null : packageName.replace('.', '/');
    }

    /**
     * 递归扫描指定的包及其子包中的类,并对所有找到的类执行Test测试，只有满足测试的类才会保留。
     *
     * @param test        用于过滤类的测试对象
     * @param packageName 被扫描的基础包名
     */
    public ResolverUtil<T> find(Test test, String packageName) {

        // 将包名转换为文件路径
        String path = getPackagePath(packageName);

        try {
            // 递归获取指定路径下的所有文件
            List<String> children = VFS.getInstance().list(path);
            for (String child : children) {
                if (child.endsWith(".class")) {
                    // 处理下面所有的类编译文件
                    addIfMatching(test, child);
                }
            }
        } catch (IOException ioe) {
            log.error("Could not read package: " + packageName, ioe);
        }
        return this;
    }

    /**
     * 如果指定的类名对应的类满足指定的条件，则将其添加到{@link #matches}中。
     *
     * @param test 用于条件判断的测试类
     * @param fqn  类的全限定名称
     */
    @SuppressWarnings("unchecked")
    protected void addIfMatching(Test test, String fqn) {
        try {
            // 将地址名称转换为类的全限定名称格式，并去掉后缀(.class)
            String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
            // 获取类加载器
            ClassLoader loader = getClassLoader();
            if (log.isDebugEnabled()) {
                log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
            }

            // 加载该类
            Class<?> type = loader.loadClass(externalName);
            // 判断是否能满足条件
            if (test.matches(type)) {
                matches.add((Class<T>) type);
            }
        } catch (Throwable t) {
            log.warn("Could not examine class '" + fqn + "'" + " due to a " +
                    t.getClass().getName() + " with message: " + t.getMessage());
        }
    }
}